package spitfire.ksim.algorithm;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import spitfire.ksim.data.KSensorSnapshot;
import spitfire.ksim.data.TSDRequestData;
import spitfire.ksim.data.TSDResponseData;
import spitfire.ksim.data.TSDRequestData.RequestEntry;

/**
 * <p>
 * FuzzyRuleBase is a database for sensor information and corresponding fuzzy 
 * rules. A record is represented by {@link FuzzyEntry}. The records are indexed
 * by sensor types to speed up the query.
 * </p>
 * <p>
 * FuzzyRuleBase provides methods for update and query, as well as methods to
 * select a semantic description(SD) for an SD request. SD request handling
 * algorithm is specified by a string and interpreted by 
 * {@link RequestAlgoFactory}.
 * </p>
 * 
 * @see FuzzyEntry
 * @see RequestAlgorithm
 * @see RuleExtractionAlgorithm
 * 
 * @author Adam
 */
public class FuzzyRuleBase {

	/** List of fuzzy entries */
	private List<FuzzyEntry> fuzzyEntryList = 
		Collections.synchronizedList(new ArrayList<FuzzyEntry>());
	private ContentSearchAlgorithm algo = new ContentSearchAlgorithm();
//	/** A string specifying the algorithm for handling SD request. */
//	private String algorithmSpec;
//	/** Algorithm for handling SD request. */
//	private RequestAlgorithm reqAlgo;
	/** Listeners to the update event of FuzzyRuleBase. */
	private List<FuzzyDBUpdatedListener> updatedListeners = 
		Collections.synchronizedList(new ArrayList<FuzzyDBUpdatedListener>());
	
//	/**
//	 * Set the SD request handling algorithm with a specification string.
//	 * 
//	 * @param algorithmSpec
//	 *            - a string specifies a combination SD request handling
//	 *            algorithm.
//	 * @see RequestAlgoFactory
//	 */
//	public void setRequestAlgorithm(String algorithmSpec) {
//		this.algorithmSpec = algorithmSpec;
//		System.out.println("Using " + algorithmSpec + " RuleBase algorithm");
//		reqAlgo = RequestAlgoFactory.getRequestAlgorithm(algorithmSpec);
//	}

//	/**
//	 * @return specification of the current request algorithm used on this gateway.
//	 */
//	public String getRequestAlgorithmSpec() {
//		return algorithmSpec;
//	}

	/**
	 * @param sensorType
	 *            - sensor type
	 * @return Fuzzy entries of the given sensor type.
	 */
	public List<FuzzyEntry> getFuzzyEntryListOf(String sensorType) {
		List<FuzzyEntry> result = new ArrayList<FuzzyEntry>();
		synchronized (fuzzyEntryList) {
			for (FuzzyEntry entry : fuzzyEntryList) {
				if (entry.getSensorType().equals(sensorType)) {
					result.add(entry);
				}
			}
		}
		return result;
	}
	
	/**
	 * @return All fuzzy entries in the rule base.
	 */
	public List<FuzzyEntry> getFuzzyEntryList() {
		return fuzzyEntryList;
	}
	
	/**
	 * @param sensorType
	 *            - sensor type
	 * @return whether this rule base has FuzzyEntry of the given sensor type.
	 */
	public boolean containsSensorType(String sensorType) {
		synchronized (fuzzyEntryList) {
			for (FuzzyEntry entry : fuzzyEntryList) {
				if (entry.getSensorType().equals(sensorType)) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * Handle the request for semantic descriptions of node and sensors in that node.
	 * 
	 * @param requestData
	 *            - raw data of the request node.
	 * @return response with semantic descriptions for sensors in the given node.
	 */
	public TSDResponseData requestForRankList(TSDRequestData requestData) {
//		if (reqAlgo == null) {
//			throw new NullPointerException("Request algorithm hasn't been set");
//		}
		String nodeId = requestData.getNodeId();
		synchronized (fuzzyEntryList) {
//			return reqAlgo.requestForSD(this, requestData);
			TSDResponseData responseData = new TSDResponseData();
			List<RequestEntry> reqEntryList = requestData.getData();
			for (RequestEntry entry : reqEntryList) {
				KSensorSnapshot snapshot = entry.sensorSnapshot;
				String sensorType = entry.sensorType;
				
				System.out.println("Calculate similarity for " + nodeId + ":" + sensorType);
				RankList rankList = algo.search(snapshot.getTimeStampList(),
						snapshot.getDataList(), getFuzzyEntryList());
//				System.out.println(nodeId + ":" + sensorType
//						+ " classified to " + sd + " (sc="
//						+ algo.getSimilarity() + ")");
				if (rankList.size() != 0) {
					responseData.putRankList(sensorType, rankList);
				}
			}
			return responseData;
		}
	}


	/**
	 * Update the rule base. In detail, if record of the given node and sensor
	 * ID exists, replace the fuzzy rule and semantic description of that
	 * record with the given ones, otherwise, insert a new record for the given
	 * node and sensor ID.
	 * 
	 * @param nodeId
	 *            - ID of the node
	 * @param sensorType
	 *            - sensor ID
	 * @param sd
	 *            - semantic description for the sensor.
	 * @param rule
	 *            - fuzzy rule
	 */
	public void updateRuleBase(String nodeId, String sensorType, String sd, FuzzyRule rule) {
		if (rule == null || nodeId == null || sensorType == null || sd == null) {
			throw new NullPointerException();
		}
		
		synchronized (fuzzyEntryList) {
			for (FuzzyEntry entry : fuzzyEntryList) {
				if (entry.match(nodeId, sensorType)) {
					// Fuzzy entry exists
					entry.setSd(sd);
					entry.setRule(rule);
					invokeFuzzyDBUpdated();
					return;
				}
			}
			// Fuzzy entry doesn't exist
			fuzzyEntryList.add(new FuzzyEntry(nodeId, sensorType, sd, rule));
		}
		invokeFuzzyDBUpdated();
	}
	
	/**
	 * Remove all entries in the rule base.
	 */
	public void clearFuzzyRuleBase() {
		fuzzyEntryList.clear();
	}
	
	/**
	 * Add a FuzzyDBUpdatedListener.
	 * @param l - listener to add.
	 */
	public void addFuzzyDBUpdatedListener(FuzzyDBUpdatedListener l) {
		updatedListeners.add(l);
	}
	
	/**
	 * Remove a FuzzyDBUpdatedListener.
	 * @param l -listener to remove.
	 */
	public void removeFuzzyDBUpdatedListener(FuzzyDBUpdatedListener l) {
		updatedListeners.remove(l);
	}
	
	/**
	 * Inform all registered FuzzyDBUpdatedListeners of rule base update event.
	 */
	public void invokeFuzzyDBUpdated() {
		synchronized (updatedListeners) {
			for (FuzzyDBUpdatedListener l : updatedListeners) {
				l.fuzzyDBUpdated();
			}
		}
	}

}
